/*
-------------------------------------------------------------------
BIE is Copyright 2001-2003 Brunswick Corp.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/bie_license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
*/
package com.webdeninteractive.sbie.system;

import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;
import java.util.Set;
import org.apache.log4j.Logger;
import java.io.IOException;
import com.webdeninteractive.sbie.RemoteSystem;
import com.webdeninteractive.sbie.config.BootConfig;
import com.webdeninteractive.sbie.config.SystemConfig;
import com.webdeninteractive.sbie.ClientFactory;
import com.webdeninteractive.sbie.Client;

/**
 * RemoteSystem providing basic functionality for SBIE.
 * <p>
 * This RemoteSystem creates a ClientFactory for itself, then creates and
 * starts the configured list of startup Clients.
 * <p>
 * It also allows for starting and stopping of clients at a later time.
 *
 * @author gfast
 * @version $Id: BasicRemoteSystem.java,v 1.2 2003/05/27 22:07:56 gdf Exp $ 
 */
public class BasicRemoteSystem implements RemoteSystem {

    private Logger logger = Logger.getLogger("com.webdeninteractive.sbie.system.BasicRemoteSystem");

    protected SystemConfig config;
    protected ClassLoader loader;
    protected ClientFactory clientFactory = null;
    protected HashMap runningClients; // name -> Client obj

    /** Processes the configuration object provided by the bootstrapper.
     *  This takes the bootstrapper's configuration and turns it into
     *  a SystemConfiguration object (which, in this implementation, is
     *  simply a casting operation).
     * @param config Configuration object provided by the bootstrapper.
     */
    public void setConfiguration( BootConfig config ) {
        // Do something to turn config into useful configuration, perhaps?
        this.config = (SystemConfig)config;
    }

    public SystemConfig getConfiguration() {
        return this.config;
    }

    /** Stores the ClassLoader provided by the bootstrapper.
     *  This ClassLoader is used for creating all classes created under this
     *  RemoteSystem.
     * @param loader ClassLoader provided by the bootstrapper.
     */
    public void setClassLoader( ClassLoader loader ) {
        logger.debug("Setting classloader = " + loader);
        this.loader = loader;
    }

    /** @return The ClassLoader saved for this RemoteSystem. */
    public ClassLoader getClassLoader() {
        return this.loader;
    }

    /** Perform this RemoteSystem's startup activity.
     *  For this implementation, this consists of instantiating a 
     *  new ClientFactory and calling <code>startClient</code> for each 
     *  Client in the configuration's list of startup clients.
     */
    public void start() {
        if ( loader == null ) {
            throw new RuntimeException("start() called before setClassLoader()");
        }
        /* make a new client factory, if we haven't yet */
        logger.debug("Creating ClientFactory...");
        clientFactory = new ClientFactory(loader);
        /* Register clients with factory */
        String[] cnames = config.getClientNames();
        for( int i=0; i<cnames.length; i++ ) {
            String classname = config.getClientImplClass(cnames[i]);
            clientFactory.register( cnames[i], classname );
        }
        /* Fire up startup-time clients */
        String[] startClients = config.getStartupClientNames();
        logger.debug("Starting " + startClients.length + " clients");
        runningClients = new HashMap(startClients.length);
        for( int i=0; i<startClients.length; i++ ) {
            startClient(startClients[i]);
        }
        
    } // start

    /** Halt this RemoteSystem.
     *  Stops any Clients which may still be running.
     */
    public void stop() {
        Set cnames = runningClients.keySet();
        Iterator running = cnames.iterator();
        while( running.hasNext() ) {
            stopClient( (String)running.next() );
        }
    }

    /** Stop and restart this RemoteSystem.
     * Equivalent to "<code>this.stop(); this.start();</code>".
     */
    public void restart() {
        stop();
        start();
    }

    /** Have this RemoteSystem create and start the given Client.
     *  Only one client of a given name may be running at a time.
     * @param name Name of Client implementation to start.
     */
    public void startClient( String name ) {
        try {
            Client c = null;
            try {
                c = clientFactory.makeClient(name);
            } catch (ClassNotFoundException e) { // newly defined, perhaps?
                logger.debug("Registering Client class for '" + name + "'");
                String implClassName = config.getClientImplClass( name );
                logger.debug("Client "+name+" impl class "+implClassName);
                clientFactory.register( name, implClassName );
                c = clientFactory.makeClient(name);
            }
            c.setName( name );
            c.setParentSystem( this );
            c.setConfiguration( this.config );
            Thread clientThread = new Thread(c);
            clientThread.setName( clientThread.getName() + ": " + name);
            clientThread.start();
            runningClients.put( name, c );
        } catch (Exception e) {
            logger.error( "Failed to start Client!", e );
        }
    }

    /** Have this RemoteSystem signal a Client to stop.
     * @param name Name of Client to stop.
     */
    public void stopClient( String name ) { 
        Client c = (Client)runningClients.get(name);
        if ( c==null ) {
            logger.debug( "stopClient requested for non-running Client '" + 
                          name + "'" );
            return;
        }
        c.halt();
        // could wait for client here?  do we want to??
        runningClients.remove(name);
    }

}


/*******************

reference process model for System:

start:
  clients-to-spawn = *config.getStartupClients()
  clientlist = ()
  foreach c in clients-to-spawn:
    startClient(c)

stop:
  foreach c in clientlist:
    stopClient(c)

restart:
  stop()
  start()

startClient(c):
  <make a new thread and spawn a "c" client>
  clientlist.add(c)

stopClient(c):
  <signal clientlist.get(c) to stop>
  clientlist.remove(c)    

*/
